/**
 ** This file is part of FK Klistret Extensions. FK Klistret Extensions 
 ** is free software: you can redistribute it and/or modify it under the 
 ** terms of the GNU General Public License as published by the Free 
 ** Software Foundation, either version 3 of the License, or (at your option) 
 ** any later version.

 ** FK Klistret Extensions is distributed in the hope that it will be 
 ** useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 ** General Public License for more details. You should have received a
 ** copy of the GNU General Public License along with FK Klistret Extensions. 
 ** If not, see <http://www.gnu.org/licenses/>
 */
package se.fk.klistret.console.ant.tasks;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;

import java.util.List;
import java.util.LinkedList;
import java.io.File;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

/**
 * 2011-07-11, The comments below are from 2005 and the code is has since
 * upgraded to the Ant 1.8 platform. The patterns and design of this project
 * should not be reused since they are not considered of any quality. The code
 * was written quickly without long term concerns.
 * <p>
 * 
 * 2005, Creates a Unix soft link
 * 
 * @author Matthew Young
 * 
 */
public class UnixLinkTask extends Task {
	/**
	 * Path object representing a potential set of paths.
	 */
	private Path searchpath;
	/**
	 * String object representing the target's path.
	 */
	private String target_path;
	/**
	 * Path to Unix link executable (default value: /usr/bin/ln)
	 */
	private String executable = "/usr/bin/ln";

	/**
	 * All source files are fetched after a series of controlls are performed.
	 * The search path for sources must not be null or empty and the sources
	 * must exist.
	 * <p>
	 * 
	 * @throws BuildException
	 *             Thrown if search path is null or empty and if a source file
	 *             does not exist.
	 * 
	 */
	private List<File> getSources() {
		List<File> results = new LinkedList<File>();

		if (searchpath == null) {
			throw new BuildException(
					"Source file(s) path null (verify task parameters)",
					getLocation());
		}

		final String[] filenames = searchpath.list();

		if (filenames.length == 0) {
			throw new BuildException(
					"Source file(s) path empty (verify file(s) exist)",
					getLocation());
		}

		for (int index = 0; index < filenames.length; index++) {
			File file = new File(filenames[index]);

			if (file.exists()) {
				results.add(file);
			} else {
				throw new BuildException("Source file (" + file.getName()
						+ ") does not exist", getLocation());
			}
		}

		return results;
	}

	/**
	 * Unix link command executed via the Java Runtime interface in a seperate
	 * process. Unix errors are captured and inserted into an Ant
	 * BuildException.
	 * <p>
	 * 
	 * @param target
	 *            <code>String</code> representing the target path.
	 * @param source
	 *            <code>String</code> representing the source path.
	 * 
	 * @throws BuildException
	 *             Unchecked throw of either an IOException or
	 *             InterruptedException.
	 * 
	 */
	private void systemLink(String target, String source) {
		try {
			String link_command = executable + " -s " + source + " " + target;

			Process link = Runtime.getRuntime().exec(link_command);

			link.waitFor();

			if (link.exitValue() != 0) {
				BufferedReader error_reader = new BufferedReader(
						new InputStreamReader(link.getErrorStream()));

				String errors = new String();

				for (;;) {
					String error = error_reader.readLine();
					if (error == null) {
						break;
					} else {
						errors = errors.concat(error);
					}
				}

				throw new BuildException("Unix error during linking of target "
						+ target + ". " + errors);
			}
		} catch (IOException ie) {
			throw new BuildException("Error creating link (IOException): "
					+ ie.getMessage(), getLocation());
		} catch (InterruptedException inte) {
			throw new BuildException(
					"Error creating link (InterruptedExcpetion): "
							+ inte.getMessage(), getLocation());
		}

	}

	/**
	 * (Required by Ant Task implementation) Fetches all sources then loops
	 * through each making target parent directories, removing former targets,
	 * and executing an Unix link.
	 * <p>
	 * 
	 * @throws BuildException
	 *             Unchecked throw of a SecurityException if target parent
	 *             directories can not be created or previous targets can not be
	 *             deleted.
	 * 
	 */
	public void execute() throws BuildException {

		List<File> sources = getSources();

		for (int index = 0; index < sources.size(); index++) {
			File source = (File) sources.get(index);

			File target = new File(target_path);

			if (target.getName().equals(".")) {
				target = new File(target.getParent() + "/" + source.getName());
				log("Target renamed to " + source.getName());
			}

			try {
				if (target.mkdirs()) {
					log("Created parent directories for target "
							+ target.getName(), Project.MSG_INFO);
				}
			} catch (SecurityException se) {
				throw new BuildException(
						"Error creating parent directories for target "
								+ target.getName() + ". " + se.getMessage(),
						getLocation());
			}

			try {
				if (target.delete()) {
					log("Deleted previous target (" + target.getPath() + ")",
							Project.MSG_INFO);
				}
			} catch (SecurityException se) {
				throw new BuildException("Error deleting previous target ("
						+ target.getPath() + ": " + se.getMessage(),
						getLocation());
			}

			systemLink(target.getPath(), source.getPath());

			log("Link created (target: " + target.getPath() + ")",
					Project.MSG_INFO);
		}

	}

	/**
	 * Sets path to Unix Link executable.
	 * <p>
	 * 
	 * @param executable
	 *            <code>String</code>
	 * 
	 */
	public void setExecutable(String executable) {
		if (executable == null) {
			throw new BuildException("Executable value is null", getLocation());
		}

		this.executable = executable;
	}

	/**
	 * Sets target. <b>target</b> parameter is reflexed into this method.
	 * <p>
	 * 
	 * @param href
	 *            <code>String</code>
	 * 
	 */
	public void setTarget(String href) {
		if (href == null) {
			throw new BuildException("Target value is null", getLocation());
		}

		this.target_path = href;
	}

	/**
	 * Sets source and thereby the location within a Path object. <b>source</b>
	 * parameter is reflexed into this method.
	 * <p>
	 * 
	 * @param href
	 *            <code>String</code>
	 * 
	 */
	public void setSource(String href) {
		if (href == null) {
			throw new BuildException("Source value is null", getLocation());
		}

		getSearchpath().setLocation(new File(href));
	}

	/**
	 * Adds a Ant direset. <b>dirset</b> parameter is reflexed into this method.
	 * <p>
	 * 
	 * @param set
	 *            <code>DirSet</code>
	 * 
	 * @throws BuildException
	 *             Thrown if the DirSet object is null;
	 * 
	 */
	public void addDirset(DirSet set) {
		if (set == null) {
			throw new BuildException("DirSet value is null", getLocation());
		}

		getSearchpath().addDirset(set);
	}

	/**
	 * Adds a Ant fileset. <b>fileset</b> parameter is reflexed into this
	 * method.
	 * <p>
	 * 
	 * @param set
	 *            <code>FileSet</code>
	 * 
	 * @throws BuildException
	 *             Thrown if the FileSet object is null;
	 * 
	 */
	public void addFileset(FileSet set) {
		if (set == null) {
			throw new BuildException("FileSet value is null", getLocation());
		}

		getSearchpath().addFileset(set);
	}

	/**
	 * Defines a Path object based on the Ant Project loaded in memory.
	 * <p>
	 * 
	 * @return <code>Path</code>
	 * 
	 */
	private Path getSearchpath() {
		if (searchpath == null) {
			searchpath = new Path(getProject());

		}
		return searchpath;
	}

	/**
	 * Appends a Path object to the initial Path object returned by the <b><font
	 * size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 */
	public void setSearchpath(Path path) {
		getSearchpath().append(path);
	}

	/**
	 * Appends a search path reference to the initial Path object returned by
	 * the <b><font size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 */
	public void setSearchpathRef(Reference reference) {
		createSearchpath().setRefid(reference);
	}

	/**
	 * Creates a Path based on the initail Path object returned by the <b><font
	 * size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 * @return <code>Path</code>
	 * 
	 */
	public Path createSearchpath() {
		return getSearchpath().createPath();
	}

	/**
	 * Creates a Path Element based on the initail Path object returned by the
	 * <b><font size="-1">getSearchpath</font></b> method.
	 * <p>
	 * 
	 * @return <code>PathElement</code>
	 * 
	 */
	public Path.PathElement createSearchpathElement() throws BuildException {
		return getSearchpath().createPathElement();
	}
}
